내부 클래스(Inner classes)
✒️ 2025-05-23 17:20 내용 수정
클래스 안에 만들어진 또 다른 클래스
- 내부 클래스와 외부 클래스가 서로 긴밀한 관계를 맺고 있다.
- 두 클래스 멤버들 간에 손쉽게 접근할 수 있다.
- 불필요한 클래스를 감춰서 코드 복잡성을 줄일 수 있다.
- 종류
- 인스턴스 클래스(멤버 클래스) : 외부 클래스를 객체화 한 상태에서 사용할 수 있다.
- 정적 클래스 : static 키워드를 사용, 외부 클래스에서 객체화 없이 쓸 수 있다.
- 지역 클래스 : 외부 클래스가 가진 메서드 내부에서 선언, 메서드가 유효할 때만 생성/소멸된다.
- 내부클래스를 private으로 만들면 return으로 다른 외부 클래스에 넘기기 어렵다.
- 클래스(Classes) 참고
public class Outer {
private String name;
//...
public class Inner {
private Stirng name;
/// ..
}
}
1. 인스턴스 클래스
외부 클래스의 인스턴스 멤버처럼 다루어지는 내부 클래스
- 클래스(Classes)에서 인스턴스 멤버는 인스턴스 생성 후 사용할 수 있는 멤버들을 말하고, 인스턴스 변수와 인스턴스 메서드가 포함된다.
- 외부 클래스 내부에서 생성하고 선언된다.
- 외부 클래스의 변수와 메서드를 사용할 수 있다.
- 외부 클래스의 필드처럼 다뤄진다.
- 주로 외부 클래스의 필드와 관련된 작업에 사용될 목적으로 생성된다.
- Java 컴파일 이후에
Outer$Inner.class형식으로 생성된다.
public class Outer {
private String name;
//...
// 인스턴스 클래스
public class Inner {
private Stirng name;
/// ..
}
}
- 인스턴스 클래스의 객체 선언
- 외부 클래스의 객체 선언 후 내부 클래스의 객체 선언
OuterClass outerInstance = new OuterClass();
Outer.Inner innerInstance = s.new Inner();
public class Outer {
private int outerValue = 10;
class Inner {
public void printOuter() {
System.out.println("outerValue = " + outerValue);
}
}
public void createInner() {
Inner inner = new Inner();
outerValue = 20;
inner.printOuter();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer. new Inner();
inner.printOuter();
outer.createInner();
outer.test();
}
}
outerValue = 10
outerValue = 20
2. 정적 내부 클래스(Nested Class)
외부 클래스에서 객체화 없이 바로 사용할 수 있는 내부 클래스
- 정적 메서드(static method)와 정적 클래스(static class)의 내부에서 외부 클래스의 인스턴스 멤버(필드, 메서드) 호출이 불가능하다.
- 정적 변수(클래스 필드), 정적 메서드(클래스 메서드)는 호출할 수 있다.
- 멤버 변수(인스턴스)는 객체 선언 후에 메모리에 등재되기 때문에 불가능하다.
- static 키워드를 가진 변수나 메서드는 메모리에서 Method 영역(클래스 레벨 정보 저장)에 저장되고, 인스턴스 객체 정보는 Heap 영역에 저장되기 때문이다.
- 외부 클래스에 직접적으로 연관된 역할을 하기보다 보조적인 역할을 처리할 때 사용한다.
- 외부 인스턴스 없이 사용할 수 있어 메모리 오버헤드가 낮다.
- 상수, static 제어자, 메모리 구조 참고.
public class Outer {
private static String name;
private int level;
public static class Inner {
public void test() {
System.out.println("name: " + name);
// 접근 불가 - 컴파일 에러
// System.out.println("level: " + level);
}
}
}
Outer.Inner inner = new Outer.Inner();
inner.test();
3. 지역 클래스
외부 클래스의 메서드나 블록 내에서 선언되는 클래스
- 메서드가 유효할 때만 생성/소멸된다.
- 지역 클래스의 스코프는 메서드 스코프나 블록 스코프이다.
- 스코프(Scope) 참고.
- 메서드가 호출될 때 스택 프레임에 적재되고, 이후 호출이 끝나 해제될 때 Garbage Collector에 의해 지역 클래스도 해제된다.
- 메모리 구조 참고.
- Java 8 버전 이전에는 지역 클래스가 본인이 속한 메서드 내의 변수를 사용하려면 해당 변수는
final이어야 했다.- 내부 클래스에서 외부 클래스의 메서드에 접근 시 지역 변수들이 내부 클래스의 숨겨진 인스턴스 변수로 구현되는 형식이었다.
- 복사본 형태로 변수를 생성하여 사용하기 때문에 복사본이 메서드의 원본 변수와 동일한 값을 유지하려면 원본 변수의 값이 변경되어선 안된다.
- 하지만 Java 8 이후엔 실질적
final처리로 인해final키워드가 없더라도 초기화 이후에 값이 변하지 않는다면final처럼 처리된다. - 특정 메서드의 초기화나 메서드의 복잡한 로직을 일회성 보조 클래스로 정의해서 사용할 때 유용하다.
- 메서드 범위 내에서의 캡슐화를 통해 외부의 멤버들과 메서드 내부의 멤버들을 분리해서 사용할 때 유용하다.
public Outer {
public void test() {
final String a = "test";
class Inner {
public void innerTest() {
System.out.println("a = " + a);
}
}
Inner inner = new Inner();
inner.innerTest();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
a = test
4. 익명 클래스
다른 내부 클래스와 달리 이름이 없는 내부 지역 클래스
- 클래스의 선언과 객체의 생성을 동시에 하므로 단 한번만 사용할 수 있고, 하나의 개체만 생성할 수 있는 일회용 클래스
- 생성자를 만들 수 없다.
- 둘 이상의 인터페이스를 구현할 수 없다.
- 단 하나의 클래스를 상속 받거나, 단 하나의 인터페이스를 구현해야 한다.
- 클래스가 가진 기능만 사용하려 할 때 사용할 수 있고, 오버라이딩해서 사용한다.
- 지역 클래스처럼 메서드 내에서 선언되고 소비되기 때문에 메서드가 가지는 변수를 사용하려고 하면 그 변수가
final이거나 실질적final(선언 후 수정 없을 경우) 이어야 한다. - 매우 제한적 용도에 사용되고, 구현해야 하는 메서드가 매우 적은 클래스를 구현할 때 사용한다.
❓) 상속, 다른 추상화 클래스랑 사용처가 어떻게 다를까, 얘들이 무슨 차이인걸까?
- 형식만 봐서는 그냥 클래스 객체를 선언한 것처럼 보이고 내부 지역 클래스같진 않은데..
✅) 기본적으로 상속, 인터페이스 구현을 쓴다!
- 특정 클래스나 인터페이스를 여러 번 기능별로 다 추가해서 쓰려고 했을 때,
한 번 쓰고 버려지면 메모리가 너무 많이 든다
- 기능을 구현화한 개체를 생성하고, 사용했던 클래스는 버리는 방법
- 원래 상속 관계에선 자식 클래스를 새로 생성해주고, 그 이후에 자식 클래스의 객체를 생성하고 나서 메서드를 사용했지만, 익명 클래스는 이런 과정을 대체하는 것으로 볼 수 있다.
;이 매우 중요!
ClassName instanceName = new ClassName() {
// 메서드 선언
// @Override
}; //;이 붙었다!
InterfaceName instanceName = new InterfaceName() {
// 메서드 선언
// @Override
};
// className instanceName = new className(); 이랑은 다르다!